use core::fmt;
use std::str::FromStr;

use thiserror::Error;

use aspect_codec::prelude::*;
use aspect_ident::DeviceIdent;

pub const MAX_ARG_BYTE_LEN: usize = 255;

#[derive(Debug, Error)]
pub enum Error {
    #[error("malformed ID '{0}'")]
    Malformed(String),

    #[error("arg too long (got {0})")]
    ArgTooLong(usize),

    #[error("unknown queue type '{0}'")]
    UnknownType(String),

    #[error("malformed device ID '{0}'")]
    MalformedDeviceId(String),
}

/// Describes a simplex channel from one device to another.
#[derive(Clone, Debug, Hash, Eq, PartialEq)]
pub struct D2DChannelSpec {
    pub src: DeviceIdent,
    pub dest: DeviceIdent,
}

impl D2DChannelSpec {
    pub fn new(src: DeviceIdent, dest: DeviceIdent) -> Self {
        Self { src, dest }
    }
}

aspect_codec::inst_struct_codec! {
    D2DChannelSpec {
        src: DeviceIdent,
        dest: DeviceIdent
    }
}

#[derive(Clone, Debug)]
pub enum MessageQueueId {
    /// Homeserver message queue, used for control messages.
    Server(String),

    /// Generic queue type, maybe make it user programmable?
    Generic(String),

    /// Device's personal queue.
    Device(DeviceIdent),

    /// Device-to-device queue, source -> dest.
    D2D(D2DChannelSpec),
    // TODO channel ID, space ID?
}

impl FromStr for MessageQueueId {
    type Err = Error;

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        match s.split_once(":") {
            Some((ty_str, arg_str)) => {
                let arg_len = arg_str.as_bytes().len();
                if arg_len > MAX_ARG_BYTE_LEN {
                    return Err(Error::ArgTooLong(arg_len));
                }

                match ty_str {
                    "server" => Ok(MessageQueueId::Server(arg_str.to_owned())),
                    "generic" => Ok(MessageQueueId::Generic(arg_str.to_owned())),
                    "device" => {
                        let did = DeviceIdent::from_str(arg_str)
                            .map_err(|_| Error::MalformedDeviceId(arg_str.to_owned()))?;
                        Ok(MessageQueueId::Device(did))
                    }
                    "d2d" => match arg_str.split_once(",") {
                        Some((lhs, rhs)) => {
                            let src_id = DeviceIdent::from_str(lhs)
                                .map_err(|_| Error::MalformedDeviceId(lhs.to_owned()))?;
                            let dest_id = DeviceIdent::from_str(rhs)
                                .map_err(|_| Error::MalformedDeviceId(rhs.to_owned()))?;
                            Ok(MessageQueueId::D2D(D2DChannelSpec::new(src_id, dest_id)))
                        }
                        None => Err(Error::Malformed(s.to_owned())),
                    },
                    _ => Err(Error::UnknownType(s.to_owned())),
                }
            }
            None => Err(Error::Malformed(s.to_owned())),
        }
    }
}

impl fmt::Display for MessageQueueId {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            Self::Device(id) => f.write_fmt(format_args!("device:{id}")),
            Self::D2D(D2DChannelSpec { src, dest }) => {
                f.write_fmt(format_args!("d2d:{src},{dest}"))
            }
            Self::Server(name) => f.write_fmt(format_args!("server:{name}")),
            Self::Generic(name) => f.write_fmt(format_args!("generic:{name}")),
        }
    }
}

aspect_codec::inst_enum_codec! {
    MessageQueueId u8 {
        0 => Server(field String),
        1 => Generic(field String),
        2 => Device(field DeviceIdent),
        3 => D2D(field D2DChannelSpec),
    }
}

/// Message entry in a queue, including contextual information.
#[derive(Clone, Debug)]
pub struct QueueEntry {
    pub idx: u64,
    pub timestamp: u64,
    pub data: Vec<u8>,
}

aspect_codec::inst_semimanual_codec! {
    QueueEntry {
        idx field u64,
        timestamp field u64,
        data frombuf Vec<u8>
    }
}
